"""
C++ code emitter for generating header and source files from IR.

This module provides the CppEmitter class which orchestrates code generation
from IR nodes to C++ files.
"""

from __future__ import annotations

from datetime import datetime
import shutil
from pathlib import Path
from typing import Any

import yaml
from jinja2 import Environment, FileSystemLoader

from orchestrator.ir.nodes import (
    ClassDeclaration,
    ConstructorDeclaration,
    FieldDeclaration,
    MethodDeclaration,
    PropertyDeclaration,
)
from orchestrator.mapping.symbol_resolver import SymbolResolver
from orchestrator.mapping.type_mapper import TypeMapper

from .formatter import CodeFormatter
from .naming import NamingConvention
from .stub_generator import StubGenerator
import re


# Types that are provided by shared stub headers.
COMMON_STUB_TYPES: set[str] = {
    "Person",
    "Pet",
    "PetOwner",
    "User",
    "Clan",
    "Asset",
    "SearchResult",
    "HttpResponse",
    "ESearchCategory",
    "Feature",
    "EventType",
}

# External dependency types that require per-class stub generation.
EXTERNAL_DEPENDENCY_TYPES: set[str] = {
    "Task",
    "WeakViewList",
    "ArrayBase",
    "IStack",
    "IQueue",
    "Bucket",
    "Random",
    "CollectionBase",
    "HttpClient",
    "WebException",
    "GroupResult_t",
    "JObject",
    "JToken",
    "JArray",
}

# Built-in or framework types that should not produce forward declarations/stubs.
BUILTIN_CSHARP_TYPES: set[str] = {
    "String",
    "Int32",
    "Int64",
    "Int16",
    "UInt32",
    "UInt64",
    "UInt16",
    "Boolean",
    "Double",
    "Float",
    "Decimal",
    "Byte",
    "SByte",
    "Object",
    "Void",
    "List",
    "Dictionary",
    "HashSet",
    "IEnumerable",
    "ICollection",
    "IList",
    "IDictionary",
    "Queue",
    "Stack",
    "LinkedList",
    "Nullable",
    "Func",
    "FuncRef",
    "Action",
    "ActionRef",
    "std",
}


class CppEmitter:
    """
    Emits C++ code from IR nodes.

    Handles header and source file generation with proper formatting,
    includes, and namespace management.
    """

    def __init__(
        self,
        type_mapper: TypeMapper | None = None,
        symbol_resolver: SymbolResolver | None = None,
        config_path: str | Path | None = None,
    ):
        """
        Initialize CppEmitter.

        Args:
            type_mapper: TypeMapper instance (creates default if None)
            symbol_resolver: SymbolResolver instance (creates default if None)
            config_path: Path to codegen-style.yaml (uses default if None)
        """
        self.type_mapper = type_mapper or TypeMapper()
        self.symbol_resolver = symbol_resolver or SymbolResolver()

        # Setup default namespace mappings
        self.symbol_resolver.setup_default_mappings()

        # Load configuration
        if config_path is None:
            default_path = (
                Path(__file__).parent.parent.parent.parent / "config" / "codegen-style.yaml"
            )
            config_path = default_path

        self.config_path = Path(config_path)
        self.config: dict[str, Any] = {}
        self._load_config()

        # Initialize naming convention and formatter based on config
        naming_config = self.config.get("naming", {})
        self.naming = NamingConvention(
            class_style=naming_config.get("class_style", "PascalCase"),
            method_style=naming_config.get("method_style", "snake_case"),
            field_style=naming_config.get("field_style", "snake_case"),
            parameter_style=naming_config.get("parameter_style", "snake_case"),
            private_prefix=naming_config.get("private_prefix", "m_"),
            constant_style=naming_config.get("constant_style", "UPPER_CASE"),
        )

        indent_config = self.config.get("indentation", {})
        format_config = self.config.get("formatting", {})
        self.formatter = CodeFormatter(
            indent_style=indent_config.get("style", "spaces"),
            indent_width=indent_config.get("width", 4),
            max_line_length=format_config.get("max_line_length", 100),
        )

        # Setup Jinja2 environment
        template_dir = Path(__file__).parent / "templates"
        self.jinja_env = Environment(
            loader=FileSystemLoader(str(template_dir)),
            trim_blocks=True,
            lstrip_blocks=True,
        )

    def _load_config(self) -> None:
        """Load code generation configuration"""
        if not self.config_path.exists():
            raise FileNotFoundError(f"Codegen config not found: {self.config_path}")

        with open(self.config_path, "r") as f:
            self.config = yaml.safe_load(f)

    def _should_skip_method(self, method_node: MethodDeclaration) -> bool:
        """
        Skip methods that are auto-generated from properties to avoid duplicates.

        The IR normalizer creates explicit getter/setter methods with metadata
        indicating they came from properties. We should skip these since properties
        are already expanded to methods elsewhere.
        """
        # Check if method has metadata indicating it was generated from a property
        if hasattr(method_node, 'metadata'):
            metadata = method_node.metadata
            if isinstance(metadata, dict):
                generated_from = metadata.get('generated_from', '')
                if generated_from in ['property_getter', 'property_setter']:
                    return True

        return False

    def _should_skip_field(self, field_node: FieldDeclaration, emitted_names: set[str]) -> bool:
        """
        Determine whether a field should be skipped during emission.

        Property normalization can introduce synthetic backing fields that map to
        the same C++ identifier as explicit fields. Track generated field names
        and drop any backing or duplicate entries that would emit twice.
        """
        is_private = "public" not in field_node.modifiers
        cpp_field_name = self.naming.convert_field_name(field_node.name, is_private=is_private)

        metadata = getattr(field_node, "metadata", {})
        if isinstance(metadata, dict):
            generated_from = metadata.get("generated_from", "")
            if generated_from in {"property_backing", "property_backing_field"} and cpp_field_name in emitted_names:
                return True

        if cpp_field_name in emitted_names:
            return True

        emitted_names.add(cpp_field_name)
        return False

    def _should_skip_field(self, field_node: FieldDeclaration, emitted_names: set[str]) -> bool:
        """
        Determine whether a field should be skipped during emission.

        Property normalization can introduce synthetic backing fields that map to
        the same C++ identifier as explicit fields. Track generated field names
        and drop any backing or duplicate entries that would emit twice.
        """
        is_private = "public" not in field_node.modifiers
        cpp_field_name = self.naming.convert_field_name(field_node.name, is_private=is_private)

        metadata = getattr(field_node, "metadata", {})
        if isinstance(metadata, dict):
            generated_from = metadata.get("generated_from", "")
            if generated_from in {"property_backing", "property_backing_field"} and cpp_field_name in emitted_names:
                return True

        if cpp_field_name in emitted_names:
            return True

        emitted_names.add(cpp_field_name)
        return False

    def _should_skip_field(self, field_node: FieldDeclaration, emitted_names: set[str]) -> bool:
        """
        Determine whether a field should be skipped during emission.

        Property normalization can introduce synthetic backing fields that map to
        the same C++ identifier as existing fields. Track the generated names and
        drop any backing fields or duplicates that would emit twice.
        """
        is_private = "public" not in field_node.modifiers
        cpp_field_name = self.naming.convert_field_name(field_node.name, is_private=is_private)

        metadata = getattr(field_node, "metadata", {})
        if isinstance(metadata, dict):
            generated_from = metadata.get("generated_from", "")
            if generated_from in {"property_backing", "property_backing_field"} and cpp_field_name in emitted_names:
                return True

        if cpp_field_name in emitted_names:
            return True

        emitted_names.add(cpp_field_name)
        return False

    def _should_skip_field(self, field_node: FieldDeclaration, emitted_names: set[str]) -> bool:
        """
        Determine whether a field should be skipped during emission.

        Property normalization can generate backing fields that map to the same
        C++ identifier as explicit fields. To avoid duplicate declarations we
        track previously emitted field names and drop property backing fields
        that would collide with an existing member.
        """
        is_private = "public" not in field_node.modifiers
        cpp_field_name = self.naming.convert_field_name(field_node.name, is_private=is_private)

        # Skip if we've already emitted a field with the same generated name
        if cpp_field_name in emitted_names:
            return True

        metadata = getattr(field_node, "metadata", {})
        if isinstance(metadata, dict):
            generated_from = metadata.get("generated_from", "")
            if generated_from in {"property_backing", "property_backing_field"} and cpp_field_name in emitted_names:
                return True

        emitted_names.add(cpp_field_name)
        return False

    def _should_skip_field(self, field_node: FieldDeclaration, emitted_names: set[str]) -> bool:
        """
        Determine whether a field should be skipped during emission.

        Property auto-generation can introduce backing fields that become duplicates
        once naming conventions are applied. We only drop those backing fields when
        a field with the same generated C++ name has already been emitted so that
        auto-properties still retain a storage member when needed.
        """
        is_private = "public" not in field_node.modifiers
        cpp_field_name = self.naming.convert_field_name(field_node.name, is_private=is_private)

        metadata = getattr(field_node, "metadata", {})
        if isinstance(metadata, dict):
            generated_from = metadata.get("generated_from", "")
            if generated_from in {"property_backing", "property_backing_field"} and cpp_field_name in emitted_names:
                return True

        if cpp_field_name in emitted_names:
            return True

        emitted_names.add(cpp_field_name)
        return False

    def emit_class(
        self, class_node: ClassDeclaration, output_dir: Path
    ) -> tuple[Path, Path]:
        """
        Emit header and source files for a class.

        Args:
            class_node: IR ClassDeclaration node
            output_dir: Output directory for generated files

        Returns:
            Tuple of (header_path, source_path)
        """
        # Convert namespace
        cpp_namespace = self.symbol_resolver.resolve_namespace(class_node.namespace)
        namespace_parts = self.symbol_resolver.get_cpp_namespace_parts(cpp_namespace)

        # Convert class name
        cpp_class_name = self.naming.convert_class_name(class_node.name)

        header_ext = self.config.get("files", {}).get("header_extension", ".hpp")
        source_ext = self.config.get("files", {}).get("source_extension", ".cpp")

        snake_name = self.naming.to_snake_case(cpp_class_name)
        header_filename = f"{snake_name}{header_ext}"
        source_filename = f"{snake_name}{source_ext}"
        stub_header_filename = f"{snake_name}_stubs{header_ext}"

        # Respect namespace hierarchy by nesting directories that mirror namespace parts
        namespace_path = Path(*namespace_parts) if namespace_parts else Path()
        target_dir = (output_dir / namespace_path) if namespace_path != Path() else output_dir

        header_path = target_dir / header_filename
        source_path = target_dir / source_filename
        stub_header_path = target_dir / stub_header_filename

        # Generate header and source contents
        header_content, stub_metadata = self._generate_header(
            class_node, cpp_class_name, namespace_parts, stub_header_filename
        )
        source_content = self._generate_source(class_node, cpp_class_name, namespace_parts)

        # Write files
        target_dir.mkdir(parents=True, exist_ok=True)

        header_path.write_text(header_content)
        source_path.write_text(source_content)

        # Ensure shared stub headers are available when required
        if stub_metadata.get("needs_common_stubs"):
            self._ensure_common_stubs(target_dir)

        # Generate per-class stub headers for missing dependencies
        missing_types: set[str] = stub_metadata.get("missing_types", set())
        if missing_types:
            self._write_stub_header(missing_types, stub_header_path)
        elif stub_header_path.exists():
            stub_header_path.unlink()

        return header_path, source_path

    def _generate_header(
        self,
        class_node: ClassDeclaration,
        cpp_class_name: str,
        namespace_parts: list[str],
        stub_header_filename: str,
    ) -> tuple[str, dict[str, Any]]:
        """Generate header file content"""
        template = self.jinja_env.get_template("class_header.hpp.jinja")

        # Collect all types used in the class for includes
        all_types = self._collect_types(class_node)
        includes = self._generate_includes(all_types, is_header=True)

        # Extract user-defined types for forward declarations
        user_types = self._extract_user_defined_types(class_node)
        # Don't forward declare the class itself
        user_types.discard(class_node.name)
        user_types.discard(cpp_class_name)

        # Identify dependency types that require stubs
        missing_types = self._identify_missing_types(user_types)
        forward_decl_types = user_types - missing_types

        # Need to check if types are used with generic parameters in base types
        template_types = self._extract_base_template_types(class_node)

        forward_declarations = ""
        if forward_decl_types:
            forward_decls = []

            # Generate forward declarations for user-defined types
            for t in sorted(forward_decl_types):
                if t in template_types:
                    # This type is used as a generic base class
                    type_params = template_types[t]
                    forward_decls.append(f"template<typename {', typename '.join(type_params)}> class {t};")
                else:
                    forward_decls.append(f"class {t};")

            forward_declarations = "\n".join(forward_decls)

        include_lines = includes.splitlines() if includes else []

        def append_user_include(line: str) -> None:
            if include_lines and include_lines[-1] != "":
                include_lines.append("")
            include_lines.append(line)

        needs_common_stubs = bool(missing_types & COMMON_STUB_TYPES)
        per_class_stub_types = missing_types - COMMON_STUB_TYPES

        if needs_common_stubs and '#include "common_stubs.hpp"' not in include_lines:
            append_user_include('#include "common_stubs.hpp"')

        if per_class_stub_types:
            include_stmt = f'#include "{stub_header_filename}"'
            if include_stmt not in include_lines:
                append_user_include(include_stmt)

        # Ensure we include headers for types that require full definitions
        definition_types = self._collect_definition_required_types(class_node)
        definition_types -= missing_types
        definition_types.discard(class_node.name)
        definition_types.discard(cpp_class_name)

        definition_types = {
            t for t in definition_types if t not in BUILTIN_CSHARP_TYPES
        }

        current_namespace = "::".join(namespace_parts) if namespace_parts else ""
        for def_type in sorted(definition_types):
            include_path = self.naming.get_include_name(current_namespace, def_type)
            include_stmt = self.formatter.format_include(include_path, system=False)
            if include_stmt not in include_lines:
                append_user_include(include_stmt)

        includes_str = "\n".join(include_lines)

        # Generate header guard
        cpp_namespace_str = "::".join(namespace_parts) if namespace_parts else ""
        header_guard = self.naming.get_header_guard(cpp_namespace_str, cpp_class_name)

        # Generate file header comment
        file_header = self._generate_file_header(
            f"{self.naming.to_snake_case(cpp_class_name)}.hpp",
            f"Header file for {cpp_class_name} class",
        )

        # Generate class comment
        class_comment = self._generate_class_comment(class_node)

        # Extract generic type parameters for the class
        class_type_params = self._extract_type_parameters(class_node)
        class_template_decl = ""
        if class_type_params:
            class_template_decl = f"template<typename {', typename '.join(class_type_params)}>"

        # Convert base classes
        base_classes = [
            f"public {self._convert_type(base)}" for base in class_node.base_types
        ]

        # Generate constructors
        constructors = self._generate_constructors(class_node, cpp_class_name)

        # Generate destructor
        destructor = self._generate_destructor(class_node, cpp_class_name)

        # Categorize methods and fields by access level
        public_methods = []
        protected_methods = []
        private_methods = []

        # Check if class has template parameters
        class_has_template = bool(class_type_params)

        for method in class_node.methods:
            # Skip duplicate property-generated methods (FIX FOR BUG #1)
            if self._should_skip_method(method):
                continue
            method_data = self._generate_method_signature(
                method, class_has_template, has_base_class=bool(class_node.base_types)
            )
            if "public" in method.modifiers:
                public_methods.append(method_data)
            elif "protected" in method.modifiers:
                protected_methods.append(method_data)
            else:
                private_methods.append(method_data)

        # Handle properties as methods (getter/setter)
        # Track method names to avoid duplicates
        existing_method_names = {m.get('name', '').lower() for m in public_methods + protected_methods + private_methods}

        for prop in class_node.properties:
            prop_methods = self._generate_property_methods(prop)
            # Filter out property methods that already exist as explicit methods
            prop_methods = [pm for pm in prop_methods if pm.get('name', '').lower() not in existing_method_names]

            if "public" in prop.modifiers:
                public_methods.extend(prop_methods)
            elif "protected" in prop.modifiers:
                protected_methods.extend(prop_methods)
            else:
                private_methods.extend(prop_methods)

        # Categorize fields
        public_fields = []
        protected_fields = []
        private_fields = []

        # Track seen field names to avoid duplicates (FIX FOR BUG #4)
        seen_field_names: set[str] = set()

        for field in class_node.fields:
            # Skip duplicate fields
            if self._should_skip_field(field, seen_field_names):
                continue

            field_data = self._generate_field_declaration(field)
            if "public" in field.modifiers:
                public_fields.append(field_data)
            elif "protected" in field.modifiers:
                protected_fields.append(field_data)
            else:
                private_fields.append(field_data)

        # Render template
        use_pragma_once = (
            self.config.get("include_guards", {}).get("style", "pragma_once") == "pragma_once"
        )

        header_content = template.render(
            file_header=file_header,
            use_pragma_once=use_pragma_once,
            header_guard=header_guard,
            includes=includes_str,
            forward_declarations=forward_declarations,
            namespace_parts=namespace_parts,
            class_name=cpp_class_name,
            class_template_decl=class_template_decl,
            class_comment=class_comment,
            base_classes=base_classes,
            constructors=constructors,
            destructor=destructor,
            public_methods=public_methods,
            protected_methods=protected_methods,
            private_methods=private_methods,
            public_fields=public_fields,
            protected_fields=protected_fields,
            private_fields=private_fields,
        )

        return header_content, {
            "missing_types": per_class_stub_types,
            "needs_common_stubs": needs_common_stubs or bool(per_class_stub_types),
        }

    def _generate_source(
        self, class_node: ClassDeclaration, cpp_class_name: str, namespace_parts: list[str]
    ) -> str:
        """Generate source file content"""
        # Check if class is templated - if so, implementations go in header
        class_type_params = self._extract_type_parameters(class_node)
        if class_type_params:
            # For template classes, return minimal source file with just include
            return f"""/// @file {self.naming.to_snake_case(cpp_class_name)}.cpp
/// @brief Implementation file for {cpp_class_name} class
/// @date {datetime.now().strftime('%Y-%m-%d')}
///
/// Generated by cpp-orchestrator
/// Note: Template class - implementation is in header file

#include "{self.naming.to_snake_case(cpp_class_name)}.hpp"
"""

        template = self.jinja_env.get_template("class_source.cpp.jinja")

        # Generate includes (header file + any additional needed)
        header_include = self.naming.get_include_name(
            "::".join(namespace_parts) if namespace_parts else "", cpp_class_name
        )

        includes_list = [self.formatter.format_include(header_include, system=False)]
        includes_list.append(self.formatter.format_include("stdexcept", system=True))

        includes = "\n".join(includes_list)

        # Generate file header
        file_header = self._generate_file_header(
            f"{self.naming.to_snake_case(cpp_class_name)}.cpp",
            f"Implementation file for {cpp_class_name} class",
        )

        # Generate constructor implementations
        constructors = self._generate_constructor_implementations(
            class_node, cpp_class_name, namespace_parts
        )

        # Generate destructor implementation
        destructor = self._generate_destructor_implementation(
            class_node, cpp_class_name, namespace_parts
        )

        # Generate method implementations
        methods = self._generate_method_implementations(
            class_node, cpp_class_name, namespace_parts
        )

        return template.render(
            file_header=file_header,
            includes=includes,
            namespace_parts=namespace_parts,
            constructors=constructors,
            destructor=destructor,
            methods=methods,
        )

    def _ensure_common_stubs(self, output_dir: Path) -> None:
        """Ensure common stub header is available in the output directory."""
        runtime_stub_path = Path(__file__).parent / "runtime_shims" / "common_stubs.hpp"
        if not runtime_stub_path.exists():
            return

        destination = output_dir / "common_stubs.hpp"
        try:
            if (
                not destination.exists()
                or runtime_stub_path.stat().st_mtime > destination.stat().st_mtime
            ):
                shutil.copyfile(runtime_stub_path, destination)
        except OSError:
            # Best-effort copy; emitters will still work without common stubs if already provided
            pass

    def _write_stub_header(self, missing_types: set[str], stub_header_path: Path) -> None:
        """Generate per-class stub header for missing external dependencies."""
        generator = StubGenerator()
        stubs = generator.generate_stubs(missing_types)
        if not stubs:
            if stub_header_path.exists():
                stub_header_path.unlink()
            return

        stub_header_path.parent.mkdir(parents=True, exist_ok=True)
        generator.write_stubs_file(stubs, stub_header_path)

    def _collect_types(self, class_node: ClassDeclaration) -> list[str]:
        """Collect all types used in a class"""
        types = []

        # Base types
        types.extend(class_node.base_types)

        # Field types
        for field in class_node.fields:
            types.append(field.field_type)

        # Property types
        for prop in class_node.properties:
            types.append(prop.property_type)

        # Method return types and parameters
        for method in class_node.methods:
            types.append(method.return_type)
            for param in method.parameters:
                types.append(param.param_type)

        # Constructor parameters
        for ctor in class_node.constructors:
            for param in ctor.parameters:
                types.append(param.param_type)

        return types

    def _extract_user_defined_types(self, class_node: ClassDeclaration) -> set[str]:
        """
        Extract user-defined types that may require forward declarations or stubs.
        """
        user_types: set[str] = set()

        for method in class_node.methods:
            user_types.update(self._extract_types_from_signature(method))

        for ctor in class_node.constructors:
            for param in ctor.parameters:
                user_types.update(self._extract_types_from_type_string(param.param_type))

        for field in class_node.fields:
            user_types.update(self._extract_types_from_type_string(field.field_type))

        for prop in class_node.properties:
            user_types.update(self._extract_types_from_type_string(prop.property_type))

        for base in getattr(class_node, "base_types", []):
            user_types.update(self._extract_types_from_type_string(base))

        return self._filter_external_types(user_types)

    def _extract_types_from_signature(self, method: MethodDeclaration) -> set[str]:
        """Extract types from a method signature."""
        types: set[str] = set()
        types.update(self._extract_types_from_type_string(method.return_type))

        for param in method.parameters:
            types.update(self._extract_types_from_type_string(param.param_type))

        return types

    def _extract_types_from_type_string(self, type_str: str) -> set[str]:
        """Extract individual type identifiers from a C# type string."""
        if not type_str:
            return set()

        cleaned = type_str.replace("[]", "")
        cleaned = cleaned.replace("?", "")

        matches = re.finditer(r"\b([A-Z][A-Za-z0-9_]*)\b", cleaned)
        extracted: set[str] = set()

        for match in matches:
            name = match.group(1)
            start = match.start()

            # Skip nested member accesses (e.g., WeakViewList<...>.Node)
            if start > 0 and cleaned[start - 1] == '.':
                continue

            if self._is_generic_type_parameter(name):
                continue
            extracted.add(name)

        return extracted

    def _filter_external_types(self, types: set[str]) -> set[str]:
        """Filter out built-in/framework types from extracted identifiers."""
        filtered: set[str] = set()
        for type_name in types:
            if not type_name:
                continue

            if type_name in BUILTIN_CSHARP_TYPES:
                continue

            if self._is_generic_type_parameter(type_name):
                continue

            filtered.add(type_name)

        return filtered

    def _collect_definition_required_types(self, class_node: ClassDeclaration) -> set[str]:
        """
        Collect types that require a full definition (e.g., stored by value).
        """
        required: set[str] = set()

        for field in class_node.fields:
            required.update(self._extract_types_from_type_string(field.field_type))

        for prop in class_node.properties:
            required.update(self._extract_types_from_type_string(prop.property_type))

        for method in class_node.methods:
            required.update(self._extract_types_from_type_string(method.return_type))
            for param in method.parameters:
                required.update(self._extract_types_from_type_string(param.param_type))

        return required

    def _identify_missing_types(self, user_types: set[str]) -> set[str]:
        """Identify types that require stub generation or shared stubs."""
        missing = set()
        for type_name in user_types:
            if type_name in EXTERNAL_DEPENDENCY_TYPES or type_name in COMMON_STUB_TYPES:
                missing.add(type_name)
        return missing

    def _extract_base_template_types(self, class_node: ClassDeclaration) -> dict[str, list[str]]:
        """
        Extract template base types and their type parameters.

        For example, from "ArrayBase<T>", returns {"ArrayBase": ["T"]}
        From "IMap<K,V>", returns {"IMap": ["K", "V"]}
        """
        template_types = {}

        if hasattr(class_node, 'base_types') and class_node.base_types:
            for base_type in class_node.base_types:
                # Match pattern: ClassName<TypeParams>
                match = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)<([^>]+)>$', base_type.strip())
                if match:
                    class_name = match.group(1)
                    type_params_str = match.group(2)

                    # Split by comma and clean up
                    type_params = [p.strip() for p in type_params_str.split(',')]

                    # Only include if type params are generic (like T, K, V)
                    # Filter out concrete types like int, string
                    generic_params = [p for p in type_params if self._is_generic_type_parameter(p)]

                    if generic_params:
                        template_types[class_name] = generic_params

        return template_types

    def _generate_includes(self, types: list[str], is_header: bool = True) -> str:
        """Generate include directives for types"""
        system_includes: set[str] = set()
        user_includes: set[str] = set()

        for type_name in types:
            if type_name in ("void", ""):
                continue

            result = self.type_mapper.map_type(type_name)
            for include in result.includes:
                # System includes start with <
                if include.startswith("<") and include.endswith(">"):
                    system_includes.add(include[1:-1])
                else:
                    system_includes.add(include)

        # Format includes
        lines = []
        for inc in sorted(system_includes):
            lines.append(self.formatter.format_include(inc, system=True))

        if user_includes:
            lines.append("")
            for inc in sorted(user_includes):
                lines.append(self.formatter.format_include(inc, system=False))

        return "\n".join(lines) if lines else ""

    def _convert_type(self, csharp_type: str) -> str:
        """Convert C# type to C++ type"""
        # Clean up malformed types before mapping
        cleaned_type = csharp_type.strip()

        # Fix incomplete generic types like "Func<T," by completing them or removing
        if '<' in cleaned_type and not cleaned_type.count('<') == cleaned_type.count('>'):
            fixed_type = cleaned_type
            if hasattr(self.type_mapper, "_fix_incomplete_generics"):
                fixed_type = self.type_mapper._fix_incomplete_generics(cleaned_type)

            if fixed_type.count('<') == fixed_type.count('>'):
                cleaned_type = fixed_type
            else:
                # Malformed generic - try to extract base type
                base_type = cleaned_type.split('<')[0].strip()
                # Return base type as a generic template parameter placeholder
                # This prevents compilation errors from malformed generics
                return f"{base_type}<void>"  # Use void as placeholder

        result = self.type_mapper.map_type(cleaned_type)
        return self._fix_nested_member_access(result.cpp_type)

    def _fix_nested_member_access(self, cpp_type: str) -> str:
        """
        Replace member access notation (e.g., Foo<T>.Bar) with valid C++ syntax.
        """
        result = cpp_type
        idx = result.find(".")

        while idx != -1:
            suffix_start = idx + 1
            suffix_end = suffix_start
            while suffix_end < len(result) and (
                result[suffix_end].isalnum() or result[suffix_end] == "_"
            ):
                suffix_end += 1

            suffix = result[suffix_start:suffix_end]
            if not suffix:
                idx = result.find(".", suffix_end)
                continue

            prefix_end = idx
            prefix_start = prefix_end - 1
            depth = 0

            while prefix_start >= 0:
                ch = result[prefix_start]
                if ch == ">":
                    depth += 1
                elif ch == "<":
                    if depth > 0:
                        depth -= 1
                    else:
                        break
                elif depth == 0 and ch in ", ([{":
                    break
                prefix_start -= 1

            prefix_start += 1
            raw_prefix = result[prefix_start:prefix_end]
            stripped_prefix = raw_prefix.strip()

            if not stripped_prefix:
                idx = result.find(".", suffix_end)
                continue

            leading_ws_len = len(raw_prefix) - len(raw_prefix.lstrip())
            leading_ws = raw_prefix[:leading_ws_len]
            core_prefix = raw_prefix.strip()

            if core_prefix.startswith("typename "):
                core_prefix = core_prefix[len("typename ") :]
                needs_typename = False
            else:
                needs_typename = "<" in core_prefix and ">" in core_prefix

            replacement_core = f"{core_prefix}::{suffix}"
            if needs_typename:
                replacement_core = f"typename {replacement_core}"

            replacement = leading_ws + replacement_core
            result = result[:prefix_start] + replacement + result[suffix_end:]

            idx = result.find(".", prefix_start + len(replacement))

        return result

    def _is_generic_type_parameter(self, type_name: str) -> bool:
        """
        Detect if a type is likely a generic type parameter.

        Common patterns:
        - Single uppercase letter: T, U, V, K, etc.
        - T followed by PascalCase: TValue, TKey, TResult, etc.
        """
        if not type_name:
            return False

        # Single uppercase letter
        if len(type_name) == 1 and type_name.isupper():
            return True

        # T followed by uppercase letter (TValue, TKey, etc.)
        if len(type_name) > 1 and type_name[0] == 'T' and type_name[1].isupper():
            return True

        return False

    def _extract_type_parameters(self, class_node: ClassDeclaration) -> list[str]:
        """
        Extract generic type parameters from a class.

        Scans all types used in methods, fields, properties to find generic parameters.
        """
        type_params = set()

        # Check method return types and parameters
        for method in class_node.methods:
            if self._is_generic_type_parameter(method.return_type):
                type_params.add(method.return_type)

            for param in method.parameters:
                # Extract type parameters from complex types like ActionRef<T>
                param_type = param.param_type
                # Check the main type
                base_type = re.sub(r'<.*>', '', param_type)  # Remove generic args
                if self._is_generic_type_parameter(base_type):
                    type_params.add(base_type)

                # Extract generic args like T from ActionRef<T>
                generic_args = re.findall(r'<([^<>]+)>', param_type)
                for arg in generic_args:
                    # Handle multiple args: <K, V>
                    for single_arg in arg.split(','):
                        single_arg = single_arg.strip()
                        if self._is_generic_type_parameter(single_arg):
                            type_params.add(single_arg)

        # Check fields
        for field in class_node.fields:
            if self._is_generic_type_parameter(field.field_type):
                type_params.add(field.field_type)

        # Check properties
        for prop in class_node.properties:
            if self._is_generic_type_parameter(prop.property_type):
                type_params.add(prop.property_type)

        # Check base types for generic parameters (FIX FOR BUG #3)
        if hasattr(class_node, 'base_types') and class_node.base_types:
            for base_type in class_node.base_types:
                # Extract generic type parameters from base class like "ArrayBase<T>" or "IList<T,V>"
                # Match pattern: <T>, <T,V>, <T1,T2>
                # Note: Using * instead of + to match single letters like T
                generic_matches = re.findall(r'<([A-Z][A-Za-z0-9,\s]*)>', base_type)
                for match in generic_matches:
                    # Split by comma in case of multiple type params like <K,V>
                    params = [p.strip() for p in match.split(',')]
                    for param in params:
                        if self._is_generic_type_parameter(param):
                            type_params.add(param)

        return sorted(type_params)

    def _extract_method_type_parameters(self, method: MethodDeclaration) -> list[str]:
        """Extract generic type parameters from a method."""
        type_params = set()

        # Check return type
        if self._is_generic_type_parameter(method.return_type):
            type_params.add(method.return_type)

        # Check parameters
        for param in method.parameters:
            param_type = param.param_type
            # Check the main type
            base_type = re.sub(r'<.*>', '', param_type)
            if self._is_generic_type_parameter(base_type):
                type_params.add(base_type)

            # Extract generic args
            generic_args = re.findall(r'<([^<>]+)>', param_type)
            for arg in generic_args:
                for single_arg in arg.split(','):
                    single_arg = single_arg.strip()
                    if self._is_generic_type_parameter(single_arg):
                        type_params.add(single_arg)

        return sorted(type_params)

    def _generate_file_header(self, filename: str, description: str) -> str:
        """Generate file header comment"""
        if not self.config.get("comments", {}).get("generate_file_headers", True):
            return ""

        comment_style = self.config.get("comments", {}).get("style", "doxygen")
        lines = [
            f"@file {filename}",
            f"@brief {description}",
            f"@date {datetime.now().strftime('%Y-%m-%d')}",
            "",
            "Generated by cpp-orchestrator",
        ]

        if comment_style == "block":
            result = ["/**"]
            result.extend(f" * {line}" if line else " *" for line in lines)
            result.append(" */")
            return "\n".join(result)
        else:
            return "\n".join(f"/// {line}" if line else "///" for line in lines)

    def _generate_class_comment(self, class_node: ClassDeclaration) -> str:
        """Generate class documentation comment"""
        if not self.config.get("comments", {}).get("generate_method_docs", True):
            return ""

        comment = f"@brief {class_node.name} class"

        comment_style = self.config.get("comments", {}).get("style", "doxygen")

        if comment_style == "block":
            return f"/** {comment} */"
        else:
            return f"/// {comment}"

    def _generate_constructors(
        self, class_node: ClassDeclaration, cpp_class_name: str
    ) -> list[dict[str, Any]]:
        """Generate constructor signatures for header"""
        constructors = []

        # Generate default constructor if configured
        if self.config.get("constructors", {}).get("generate_default", True) and not class_node.constructors:
            sig = f"{cpp_class_name}()"
            if self.config.get("constructors", {}).get("use_default_keyword", True):
                sig += " = default"

            constructors.append(
                {"signature": sig, "comment": self.formatter.format_comment("Default constructor", "doxygen")}
            )

        # Generate constructors from IR
        for ctor in class_node.constructors:
            params = []
            for param in ctor.parameters:
                cpp_type = self._convert_type(param.param_type)
                cpp_name = self.naming.convert_parameter_name(param.name)
                params.append(f"{cpp_type} {cpp_name}")

            signature = f"{cpp_class_name}({', '.join(params)})"

            comment = self.formatter.format_comment(f"Constructor", "doxygen")

            constructors.append({"signature": signature, "comment": comment})

        return constructors

    def _generate_destructor(
        self, class_node: ClassDeclaration, cpp_class_name: str
    ) -> dict[str, Any] | None:
        """Generate destructor signature for header"""
        is_virtual = (
            class_node.base_types
            or self.config.get("methods", {}).get("virtual_destructors", True)
        )

        sig = ""
        if is_virtual:
            sig += "virtual "
        sig += f"~{cpp_class_name}()"

        if self.config.get("constructors", {}).get("use_default_keyword", True):
            sig += " = default"

        comment = self.formatter.format_comment("Destructor", "doxygen")

        return {"signature": sig, "comment": comment}

    def _generate_method_signature(
        self,
        method: MethodDeclaration,
        class_has_template: bool = False,
        has_base_class: bool = False,
    ) -> dict[str, Any]:
        """Generate method signature for header"""
        # Extract method-level type parameters
        method_type_params = self._extract_method_type_parameters(method)

        # Convert return type
        cpp_return_type = self._convert_type(method.return_type)

        # Convert method name
        cpp_method_name = self.naming.convert_method_name(method.name)

        # Convert parameters
        params = []
        for param in method.parameters:
            # Clean parameter name (remove @ prefix and any malformed type prefixes)
            param_name = param.name
            # Handle malformed parameter names like "T> @delegate"
            if '>' in param_name:
                # Extract just the identifier after >
                param_name = param_name.split('>')[-1].strip()
            param_name = param_name.lstrip('@')  # Remove @ prefix

            cpp_type = self._convert_type(param.param_type)
            cpp_name = self.naming.convert_parameter_name(param_name)
            params.append(f"{cpp_type} {cpp_name}")

        # Build signature
        signature = f"{cpp_return_type} {cpp_method_name}({', '.join(params)})"

        # Add qualifiers
        qualifiers = []
        if self.config.get("methods", {}).get("const_correctness", True):
            # Simple heuristic: methods starting with "Get" or "Is" are const
            if method.name.startswith(("Get", "Is", "Has")):
                qualifiers.append("const")

        if method.is_virtual:
            signature = "virtual " + signature

        if qualifiers:
            signature += " " + " ".join(qualifiers)

        if (
            method.is_override
            and has_base_class
            and self.config.get("methods", {}).get("override_keyword", True)
        ):
            signature += " override"

        if method.is_abstract:
            signature += " = 0"

        # Add method template declaration if needed (only if class doesn't have template)
        template_decl = ""
        if method_type_params and not class_has_template:
            template_decl = f"template<typename {', typename '.join(method_type_params)}>"

        # Generate comment
        comment = self.formatter.format_comment(f"{method.name} method", "doxygen")

        return {"signature": signature, "comment": comment, "name": method.name, "template_decl": template_decl}

    def _generate_property_methods(
        self, prop: PropertyDeclaration
    ) -> list[dict[str, Any]]:
        """Generate getter/setter methods for a property"""
        methods = []
        cpp_type = self._convert_type(prop.property_type)
        cpp_prop_name = self.naming.convert_method_name(prop.name)

        # Generate getter
        if prop.has_getter:
            getter_name = f"get_{cpp_prop_name}"
            getter_sig = f"{cpp_type} {getter_name}() const"
            methods.append(
                {
                    "signature": getter_sig,
                    "comment": self.formatter.format_comment(f"Get {prop.name}", "doxygen"),
                    "name": getter_name,
                }
            )

        # Generate setter
        if prop.has_setter:
            setter_name = f"set_{cpp_prop_name}"
            setter_sig = f"void {setter_name}({cpp_type} value)"
            methods.append(
                {
                    "signature": setter_sig,
                    "comment": self.formatter.format_comment(f"Set {prop.name}", "doxygen"),
                    "name": setter_name,
                }
            )

        return methods

    def _generate_field_declaration(self, field: FieldDeclaration) -> dict[str, Any]:
        """Generate field declaration"""
        cpp_type = self._convert_type(field.field_type)
        is_private = "private" in field.modifiers or "public" not in field.modifiers
        cpp_name = self.naming.convert_field_name(field.name, is_private=is_private)

        if field.initializer:
            declaration = f"{cpp_type} {cpp_name} = {field.initializer}"
        else:
            declaration = f"{cpp_type} {cpp_name}"

        comment = self.formatter.format_comment(f"{field.name} field", "doxygen")

        return {"declaration": declaration, "comment": comment}

    def _generate_constructor_implementations(
        self, class_node: ClassDeclaration, cpp_class_name: str, namespace_parts: list[str]
    ) -> list[dict[str, Any]]:
        """Generate constructor implementations for source file"""
        constructors = []

        # Only generate implementations for non-default constructors
        for ctor in class_node.constructors:
            params = []
            for param in ctor.parameters:
                cpp_type = self._convert_type(param.param_type)
                cpp_name = self.naming.convert_parameter_name(param.name)
                params.append(f"{cpp_type} {cpp_name}")

            # Use qualified name for constructor
            qualified_name = f"{cpp_class_name}::{cpp_class_name}"
            signature = f"{qualified_name}({', '.join(params)})"

            body = ctor.body if ctor.body else None

            constructors.append({"signature": signature, "body": body})

        return constructors

    def _generate_destructor_implementation(
        self, class_node: ClassDeclaration, cpp_class_name: str, namespace_parts: list[str]
    ) -> dict[str, Any] | None:
        """Generate destructor implementation for source file"""
        # Only generate if not using = default
        if self.config.get("constructors", {}).get("use_default_keyword", True):
            return None

        qualified_name = f"{cpp_class_name}::~{cpp_class_name}"
        signature = f"{qualified_name}()"

        return {"signature": signature, "body": None}

    def _generate_method_implementations(
        self, class_node: ClassDeclaration, cpp_class_name: str, namespace_parts: list[str]
    ) -> list[dict[str, Any]]:
        """Generate method implementations for source file"""
        methods = []

        for method in class_node.methods:
            # Skip duplicate property-generated methods (FIX FOR BUG #1)
            if self._should_skip_method(method):
                continue
            # Skip abstract methods
            if method.is_abstract:
                continue

            cpp_return_type = self._convert_type(method.return_type)
            cpp_method_name = self.naming.convert_method_name(method.name)

            params = []
            for param in method.parameters:
                # Clean parameter name (remove @ prefix and any malformed type prefixes)
                param_name = param.name
                # Handle malformed parameter names like "T> @delegate"
                if '>' in param_name:
                    # Extract just the identifier after >
                    param_name = param_name.split('>')[-1].strip()
                param_name = param_name.lstrip('@')  # Remove @ prefix

                cpp_type = self._convert_type(param.param_type)
                cpp_name = self.naming.convert_parameter_name(param_name)
                params.append(f"{cpp_type} {cpp_name}")

            # Use qualified name
            qualified_name = f"{cpp_class_name}::{cpp_method_name}"
            signature = f"{cpp_return_type} {qualified_name}({', '.join(params)})"

            # Add const qualifier if needed
            if self.config.get("methods", {}).get("const_correctness", True):
                if method.name.startswith(("Get", "Is", "Has")):
                    signature += " const"

            body = method.body if method.body else None

            methods.append({"signature": signature, "body": body, "name": cpp_method_name})

        # Generate property method implementations
        # Track method names to avoid duplicates
        existing_method_names = {m.get('name', '').lower() for m in methods}

        for prop in class_node.properties:
            prop_methods = self._generate_property_method_implementations(
                prop, cpp_class_name
            )
            # Filter out property methods that already exist as explicit methods
            prop_methods = [pm for pm in prop_methods if pm.get('name', '').lower() not in existing_method_names]
            methods.extend(prop_methods)

        return methods

    def _generate_property_method_implementations(
        self, prop: PropertyDeclaration, cpp_class_name: str
    ) -> list[dict[str, Any]]:
        """Generate property getter/setter implementations"""
        methods = []
        cpp_type = self._convert_type(prop.property_type)
        cpp_prop_name = self.naming.convert_method_name(prop.name)

        # Backing field name
        backing_field = f"m_{cpp_prop_name}"

        # Getter
        if prop.has_getter:
            getter_name = f"get_{cpp_prop_name}"
            qualified_name = f"{cpp_class_name}::{getter_name}"
            signature = f"{cpp_type} {qualified_name}() const"

            body = prop.getter_body if prop.getter_body else f"    return {backing_field};"

            methods.append({"signature": signature, "body": body, "name": getter_name})

        # Setter
        if prop.has_setter:
            setter_name = f"set_{cpp_prop_name}"
            qualified_name = f"{cpp_class_name}::{setter_name}"
            signature = f"void {qualified_name}({cpp_type} value)"

            body = prop.setter_body if prop.setter_body else f"    {backing_field} = value;"

            methods.append({"signature": signature, "body": body, "name": setter_name})

        return methods
